package com.proto {
	/**
	 * ...
	 * @author chenlong 2017/5/27 13:44
	 */
	public class Tokenizer {
		
		public var source:String;
        public var index:int;
        public var line:int;
        public var stack:Vector.<String>;
        public var readingString:Boolean;
        public var stringEndsWith:String;
        
		/**注释**/
		public var comments:Object = {};
		
		/**
		 * 获取对应行注释
		 * @param	line
		 * @return
		 */
		public function getComment( line:int = -1 ):String{
			
			line = line == -1 ? this.line : line;
			return comments[ line ] || "";
		}
		
		private var _stringOpen:*;
		/**
         * Constructs a new Tokenizer.
         * ProtoBuf.DotProto.Tokenizer
         * prototype tokenizer
         * @param {string} proto Proto to tokenize
         */
		public function Tokenizer( proto:String ) {
			
			this.source = proto + "";
			this.index = 0;
			this.line = 1;
			this.stack = new Vector.<String>;
			this._stringOpen = null;
		}
		
		private function _readString():*{
			
			var re:RegExp = this._stringOpen === '"' ? Lang.STRING_DQ : Lang.STRING_SQ;
            re.lastIndex = this.index - 1; // Include the open quote
            var match:Object = re.exec(this.source);
            if (!match)
                throw new Error("unterminated string");
            this.index = re.lastIndex;
            this.stack.push(this._stringOpen);
            this._stringOpen = null;
            return match[1];
		}
		
		/**
		 * 当前获得的代码注释
		 */
		private var _comments:String = "";
		
		public function next():String{
			if (this.stack.length > 0)
                return this.stack.shift();
            if (this.index >= this.source.length)
                return null;
            if (this._stringOpen !== null)
                return this._readString();

            var repeat:Boolean;
            var prev:String;
            var next:String;
			
			var commentsIndex:int = -1;//注释索引 cl 2017/5/31 16:13
			
            do {
				commentsIndex = -1;
				
                repeat = false;

                // Strip white spaces
                while (Lang.WHITESPACE.test(next = this.source.charAt(this.index))) {
                    if (next === '\n')
                        ++this.line;
                    if (++this.index === this.source.length)
                        return null;
                }

                // Strip comments 代码注释 
                if (this.source.charAt(this.index) === '/') {
                    ++this.index;
                    if (this.source.charAt(this.index) === '/') { // Line
						commentsIndex = this.index;
                        while (this.source.charAt(++this.index) !== '\n')
                            if (this.index == this.source.length)
                                return null;						
                        ++this.index;
						//解析注释 cl 2017.5.31
						this.setCommnet( commentsIndex - 1 , this.index - 1 , this.line );
                        ++this.line;
                        repeat = true;
                    } else if ((next = this.source.charAt(this.index)) === '*') { /* Block */
						commentsIndex = this.index;
                        do {
                            if (next === '\n')
                                ++this.line;
                            if (++this.index === this.source.length)
                                return null;
                            prev = next;
                            next = this.source.charAt(this.index);
                        } while (prev !== '*' || next !== '/');
                        ++this.index;						
						//解析注释 cl 2017.5.31
						this.setCommnet( commentsIndex - 1 , this.index - 1 , this.line );
                        repeat = true;
                    } else
                        return '/';
                }
            } while (repeat);
			
            if (this.index === this.source.length)
                return null;

            // Read the next token
            var end:int = this.index;
            Lang.DELIM.lastIndex = 0;
            var delim:Boolean = Lang.DELIM.test(this.source.charAt(end++));
            if (!delim)
                while(end < this.source.length && !Lang.DELIM.test(this.source.charAt(end)))
                    ++end;
            var token:String = this.source.substring(this.index, this.index = end);
            if (token === '"' || token === "'")
                this._stringOpen = token;
			//if ( token.indexOf("//") != -1 ){
				//trace("xx");
			//}
			//trace("---------token: "+token);
            return token;
		}
		
		/**
		 * 设置对应行注释
		 * @param	start
		 * @param	end
		 * @param	line
		 */
		private function setCommnet( start:int , end:int , line:int ):void{			
			this._comments = start != -1 ? this.source.slice( start , end ) : "";			
			if (this._comments!=""){
				//trace("_comments: " + this._comments + " line: " + this.line );
			}
			this.comments[ this.line ] = this._comments;
		}
		
        public function peek():String{
			
			if (this.stack.length === 0) {
                var token:String = this.next();
                if (token === null)
                    return null;
                this.stack.push(token);
            }
            return this.stack[0];
		}
		
		public function skip( expected:String ):void{
			
			var actual:String = this.next();
            if (actual !== expected)
                throw new Error("illegal '"+actual+"', '"+expected+"' expected");
		}
		
		public function omit( expected:String ):Boolean{
			
			if (this.peek() === expected) {
                this.next();
                return true;
            }
            return false;
		}
		
        public function get toString():String{
			return "Tokenizer ("+this.index+"/"+this.source.length+" at line "+this.line+")";
		}
	}

}